#pragma once
#include <cmath>

namespace MASGK
{
	struct float3;
	struct float4;
	struct float4x4;

	float dot(float3, float3);
	float dot(float4, float4);
	float3 cross(float3, float3);

	struct float3
	{
		union
		{
			struct
			{
				float	x;
				float	y;
				float	z;
			};

			struct
			{
				float	r;
				float	g;
				float	b;
			};
		};
		
		float3(): x(0.0f), y(0.0f), z(0.0f) {}

		float3(float _x, float _y, float _z): x(_x), y(_y), z(_z) {}

		float length()
		{
			return sqrtf(x*x + y*y + z*z);
		}

		float3 normalize()
		{
			float len = length();
			
			return float3(x / len, y / len, z / len);
		}

		void normalizeMe()
		{
			float len = length();
			
			x /= len;
			y /= len;
			z /= len;
		}

		float3 operator+ (float3 _vec)
		{
			return float3(x + _vec.x, y + _vec.y, z + _vec.z);
		}

		float3 operator- (float3 _vec)
		{
			return float3(x - _vec.x, y - _vec.y, z - _vec.z);
		}

		float3 operator* (float3 _vec)
		{
			return float3(x * _vec.x, y * _vec.y, z * _vec.z);
		}

		float3 operator* (float _val)
		{
			return float3(x * _val, y * _val, z * _val);
		}

		friend float3 operator*(float _val, float3 _vec)
		{
			return _vec * _val;
		}

		float3 reflect(float3 _norm)
		{
			return -2.0f * dot(*this, _norm) * _norm + *this;
		}
	};


	struct float4
	{
		union
		{
			struct
			{
				float	x;
				float	y;
				float	z;
				float	w;
			};

			struct
			{
				float	r;
				float	g;
				float	b;
				float	a;
			};
		};
		
		float4(): x(0.0f), y(0.0f), z(0.0f), w(1.0f) {}

		float4(float _x, float _y, float _z, float _w = 1.0f): x(_x), y(_y), z(_z), w(_w) {}

		float length()
		{
			return sqrtf(x*x + y*y + z*z + w*w);
		}

		float4 normalize()
		{
			float len = length();
			
			return float4(x / len, y / len, z / len, w / len);
		}

		void normalizeMe()
		{
			float len = length();
			
			x /= len;
			y /= len;
			z /= len;
			w /= len;
		}

		float4 operator+ (float4 _vec)
		{
			return float4(x + _vec.x, y + _vec.y, z + _vec.z, w + _vec.w);
		}

		float4 operator* (float4 _vec)
		{
			return float4(x * _vec.x, y * _vec.y, z * _vec.z, w * _vec.w);
		}

		float4 operator* (float _val)
		{
			return float4(x * _val, y * _val, z * _val, w * _val);
		}

		friend float4 operator*(float _val, float4 _vec)
		{
			return _vec * _val;
		}

		float4 reflect(float4 _norm)	// Not checked!
		{
			return -2.0f * dot(*this, _norm) * _norm + *this;
		}
	};

	struct float4x4
	{
		float4			column[4];

		float4x4()
		{
			column[0] = float4();
			column[1] = float4();
			column[2] = float4();
			column[3] = float4();
		}

		float4x4 (float4 _col0, float4 _col1, float4 _col2, float4 _col3)
		{
			column[0] = _col0;
			column[1] = _col1;
			column[2] = _col2;
			column[3] = _col3;
		}

		float4 operator* (float4 _vec)
		{
			return float4
				(
					this->column[0].x * _vec.x +
					this->column[1].x * _vec.y +
					this->column[2].x * _vec.z +
					this->column[3].x * _vec.w,

					this->column[0].y * _vec.x +
					this->column[1].y * _vec.y +
					this->column[2].y * _vec.z +
					this->column[3].y * _vec.w,

					this->column[0].z * _vec.x +
					this->column[1].z * _vec.y +
					this->column[2].z * _vec.z +
					this->column[3].z * _vec.w,

					this->column[0].w * _vec.x +
					this->column[1].w * _vec.y +
					this->column[2].w * _vec.z +
					this->column[3].w * _vec.w
				);
		}

		float4x4 operator* (float4x4 _mat)
		{
			return float4x4
				(
					float4
					(
						this->column[0].x * _mat.column[0].x +
						this->column[1].x * _mat.column[0].y +
						this->column[2].x * _mat.column[0].z +
						this->column[3].x * _mat.column[0].w,

						this->column[0].y * _mat.column[0].x +
						this->column[1].y * _mat.column[0].y +
						this->column[2].y * _mat.column[0].z +
						this->column[3].y * _mat.column[0].w,

						this->column[0].z * _mat.column[0].x +
						this->column[1].z * _mat.column[0].y +
						this->column[2].z * _mat.column[0].z +
						this->column[3].z * _mat.column[0].w,

						this->column[0].w * _mat.column[0].x +
						this->column[1].w * _mat.column[0].y +
						this->column[2].w * _mat.column[0].z +
						this->column[3].w * _mat.column[0].w
					),

					float4
					(
						this->column[0].x * _mat.column[1].x +
						this->column[1].x * _mat.column[1].y +
						this->column[2].x * _mat.column[1].z +
						this->column[3].x * _mat.column[1].w,

						this->column[0].y * _mat.column[1].x +
						this->column[1].y * _mat.column[1].y +
						this->column[2].y * _mat.column[1].z +
						this->column[3].y * _mat.column[1].w,

						this->column[0].z * _mat.column[1].x +
						this->column[1].z * _mat.column[1].y +
						this->column[2].z * _mat.column[1].z +
						this->column[3].z * _mat.column[1].w,

						this->column[0].w * _mat.column[1].x +
						this->column[1].w * _mat.column[1].y +
						this->column[2].w * _mat.column[1].z +
						this->column[3].w * _mat.column[1].w
					),

					float4
					(
						this->column[0].x * _mat.column[2].x +
						this->column[1].x * _mat.column[2].y +
						this->column[2].x * _mat.column[2].z +
						this->column[3].x * _mat.column[2].w,

						this->column[0].y * _mat.column[2].x +
						this->column[1].y * _mat.column[2].y +
						this->column[2].y * _mat.column[2].z +
						this->column[3].y * _mat.column[2].w,

						this->column[0].z * _mat.column[2].x +
						this->column[1].z * _mat.column[2].y +
						this->column[2].z * _mat.column[2].z +
						this->column[3].z * _mat.column[2].w,

						this->column[0].w * _mat.column[2].x +
						this->column[1].w * _mat.column[2].y +
						this->column[2].w * _mat.column[2].z +
						this->column[3].w * _mat.column[2].w
					),

					float4
					(
						this->column[0].x * _mat.column[3].x +
						this->column[1].x * _mat.column[3].y +
						this->column[2].x * _mat.column[3].z +
						this->column[3].x * _mat.column[3].w,

						this->column[0].y * _mat.column[3].x +
						this->column[1].y * _mat.column[3].y +
						this->column[2].y * _mat.column[3].z +
						this->column[3].y * _mat.column[3].w,

						this->column[0].z * _mat.column[3].x +
						this->column[1].z * _mat.column[3].y +
						this->column[2].z * _mat.column[3].z +
						this->column[3].z * _mat.column[3].w,

						this->column[0].w * _mat.column[3].x +
						this->column[1].w * _mat.column[3].y +
						this->column[2].w * _mat.column[3].z +
						this->column[3].w * _mat.column[3].w
					)
				);
		}

		float4& float4x4::operator[] (const int _idx)
		{
			return column[_idx];
		}
	};

	int min (int, int, int);
	int max (int, int, int);

	typedef float4 color;

	#define PI 3.14159265359f

	float4 mul(float4, float4x4);
}